
#ifndef INCLUDE_SHM_LOG_H
#define INCLUDE_SHM_LOG_H

#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <time.h>

#if _WIN32
#include "shm_queue_win.h"
#else
#include "shm_queue_linux.h"
#endif

#define ENABLE_LOG 1

enum {
  SHM_LOG_MAX_NAME = 128,
  SHM_LOG_MAX_FILE_NAME = 1024,
  SHM_LOG_MAX_CHANNEL = 32,
  SHM_LOG_1MB = 1024 * 1024
};

typedef enum shm_log_level { 
  LOG_TRACE, 
  LOG_DEBUG, 
  LOG_INFO, 
  LOG_WARN, 
  LOG_ERROR, 
  LOG_FATAL 
} shm_log_level_t;


#if ENABLE_LOG
#define log_trace(...) shm_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define log_debug(...) shm_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define log_info(...)  shm_log(LOG_INFO,  __FILE__, __LINE__, __VA_ARGS__)
#define log_warn(...)  shm_log(LOG_WARN,  __FILE__, __LINE__, __VA_ARGS__)
#define log_error(...) shm_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define log_fatal(...) shm_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)

#else
#define log_trace(...) (void)(0)
#define log_debug(...) (void)(0)
#define log_info(...)  (void)(0)
#define log_warn(...)  (void)(0)
#define log_error(...) (void)(0)
#define log_fatal(...) (void)(0)

#endif

SHM_API int  shm_log_init(const char* config_file, const char* name);
SHM_API int  shm_log_init2(const char* name, uint32_t  max_size, 
  uint32_t max_count, shm_log_level_t log_level);
SHM_API void shm_log_destroy(void);
SHM_API void shm_log(int level, const char *file, int line, const char *fmt, ...);
SHM_API void shm_log_str(int level, const char *file, int line, const char *str);

#endif //INCLUDE_SHM_LOG_H

#ifdef SHM_LOG_IMPLEMENTATION

#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <pthread.h>
#include "shm_ini.h"

static bool _shm_log_inited = false;
static shm_queue_t _shm_log_queue;
static pthread_mutex_t _shm_log_lock;
static shm_log_level_t _shm_log_level = LOG_INFO;

static const char* shm_log_level_strings[] = {
  "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
};

struct shm_log_config {
  const char *name;
  char shm_queue[SHM_LOG_MAX_NAME];
  uint32_t elem_size;
  uint32_t elem_count;
  shm_log_level_t log_level;
};

static int _shm_log_ini_parse(void *user, const char *section, const char *name,
          const char *value) {
  struct shm_log_config *config = (struct shm_log_config *)user;
  if (strcmp(section, config->name) == 0) {
    if (strcmp(name, "shm_queue") == 0) {
      strncpy(config->shm_queue, value, SHM_LOG_MAX_NAME);
    } else if (strcmp(name, "elem_size") == 0) {
      config->elem_size = atoi(value);
    } else if (strcmp(name, "elem_count") == 0) {
      config->elem_count = atoi(value);
    } else if (strcmp(name, "log_level") == 0) {
      if (strcmp(value, "TRACE") == 0) {
        config->log_level = LOG_TRACE;
      } else if (strcmp(value, "DEBUG") == 0) {
        config->log_level = LOG_DEBUG;
      } else if (strcmp(value, "INFO") == 0) {
        config->log_level = LOG_INFO;
      } else if (strcmp(value, "WARN") == 0) {
        config->log_level = LOG_WARN;
      } else if (strcmp(value, "ERROR") == 0) {
        config->log_level = LOG_ERROR;
      } else if (strcmp(value, "FATAL") == 0) {
        config->log_level = LOG_FATAL;
      } else {
        config->log_level = LOG_WARN;
      }
    }
  }
  return 0;
}

SHM_API int shm_log_init(const char* config_file, const char* name) {
  struct shm_log_config config;
  FILE *file = fopen(config_file, "r");
  if (!file) {
    return SHM_QUEUE_ERROR_OPEN;
  }
  config.name = name;
  config.shm_queue[0] = '\0';
  config.elem_size = 1024;
  config.elem_count = 1024;
  config.log_level = LOG_INFO;
  ini_parse_file(file, _shm_log_ini_parse, &config);
  fclose(file);
  return shm_log_init2(config.shm_queue, config.elem_size, 
    config.elem_count, config.log_level);
}

SHM_API int shm_log_init2(const char* name, uint32_t  max_size, 
    uint32_t  max_count, shm_log_level_t log_level) {
  int rslt;
  if (!_shm_log_inited) {
    shm_queue_options_t options;
    options.name = name;
    options.elem_count = max_count;
    options.elem_size = max_size;
    options.mode = SHM_QUEUE_MODE_WRITE;
    _shm_log_level = log_level;
    rslt = shm_queue_open(&_shm_log_queue, &options);
    if (rslt == SHM_QUEUE_ERROR_OK) {
      pthread_mutex_init(&_shm_log_lock, NULL);
      _shm_log_inited = true;
    }
  } else {
    rslt = SHM_QUEUE_ERROR_OK;
  }
  return rslt;
}

SHM_API void shm_log_destroy(void) {
  if (_shm_log_inited) {
    _shm_log_inited = false;
    shm_queue_close(&_shm_log_queue);
    pthread_mutex_destroy(&_shm_log_lock);
  }
}


static char const* _shm_log_file_name(char const* file) {
  char const* name;
  if (!file) {
    return "";
  }

#if _WIN32
  name = strrchr(file, '\\');
#else
  name = strrchr(file, '/');
#endif

  if (name) {
    return name + 1;
  } else {
    return file;
  }
}

SHM_API void shm_log(int level, const char *file, int line, const char *fmt, ...) {
  struct timeval now;
  struct tm tm;
  uint32_t ms;
  char tm_buf[64];
  void* buf;
  uint32_t buf_size;
  va_list ap;

  if (!_shm_log_inited || level < _shm_log_level) {
    return;
  }

  gettimeofday(&now, NULL);
  localtime_r(&now.tv_sec, &tm);
  ms = now.tv_usec / 1000;
  tm_buf[strftime(tm_buf, sizeof(tm_buf), "%Y-%m-%d %H:%M:%S", &tm)] = '\0';

  pthread_mutex_lock(&_shm_log_lock);

  va_start(ap, fmt);

  if (shm_queue_ncwrite_begin(&_shm_log_queue, &buf, &buf_size) == SHM_QUEUE_ERROR_OK) {
    char* str = (char*)buf;
    int rslt = snprintf(
      str, buf_size, "%s %-5s %s:%d: ",
      shm_log_level_strings[level], tm_buf,
      _shm_log_file_name(file), line);
    if (rslt > 0) {
      int rslt2 = vsnprintf(str + rslt, buf_size - rslt, fmt, ap);
      if (rslt2 > 0) {
        rslt += rslt2;
      }
    }

    if (rslt > 0)
      shm_queue_ncwrite_finish(&_shm_log_queue, rslt);
  }

  va_end(ap);
  pthread_mutex_unlock(&_shm_log_lock);
}


SHM_API void shm_log_str(int level, const char *file, int line, const char *str) {
  struct timeval now;
  struct tm tm;
  uint32_t ms;
  char tm_buf[64];
  void* buf;
  uint32_t buf_size;

  if (!_shm_log_inited || level < _shm_log_level) {
    return;
  }

  gettimeofday(&now, NULL);
  localtime_r(&now.tv_sec, &tm);
  ms = now.tv_usec / 1000;
  tm_buf[strftime(tm_buf, sizeof(tm_buf), "%Y-%m-%d %H:%M:%S", &tm)] = '\0';

  pthread_mutex_lock(&_shm_log_lock);

  if (shm_queue_ncwrite_begin(&_shm_log_queue, &buf, &buf_size) == SHM_QUEUE_ERROR_OK) {
    int rslt = snprintf(
      (char*)buf, buf_size, "%s %-5s %s:%d: %s",
      shm_log_level_strings[level], tm_buf,
      _shm_log_file_name(file), line, str);

    if (rslt > 0)
      shm_queue_ncwrite_finish(&_shm_log_queue, rslt);
  }
  pthread_mutex_unlock(&_shm_log_lock);
}

#undef SHM_LOG_IMPLEMENTATION
#endif //SHM_LOG_IMPLEMENTATION

